<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8" />
	<title>音乐播放器</title>
	<style type="text/css">
		body{color: #FFF;}
		#cas{
			position: absolute;
			left:0;top:0;bottom: 0;right: 0;
			margin: auto;
			background:#000;
			z-index: -1;
		}
		#musicFile{display: none;}
		#mp3{visibility: hidden;}
	</style>
</head>
<body style="background:#000">

	<canvas id="cas" width="900" height="540"></canvas>

	<audio preload autoplay id="mp3"></audio>
	
	<button id="author">听作者的歌</button>
	<input type="file" id="musicFile"/>
	<button id="musicFileBtn">点击选择自己的歌</button>
	<button id="muti">静音</button>

	<script type="text/javascript" charset="utf-8">
		(function(){
			var musics = ['../music.mp3','../music2.mp3'];

			var canvas = document.getElementById("cas"),
				ctx = canvas.getContext("2d");

			//停止动画开关
			var paintStop = false;

			
			// audioSource 为音频源，bufferSource为buffer源
			var audioSource , bufferSource;

			//实例化音频对象
			var AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext;
			var AC = new AudioContext();

			// analyser为analysernode，具有频率的数据，用于创建数据可视化
			var analyser = AC.createAnalyser();
			// gain为gainNode，音频的声音处理模块
			var gain = AC.createGain();
			gain.gain.value = 1;

			//计时器
			var RAF = (function() {
				return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
					window.setTimeout(callback, 1000 / 60);
				};
			})();

			//播放音乐
			var audio = document.getElementById("mp3");
			audio.onended = function(){
				chooseMusic();
			}
			chooseMusic();

			//静音按钮
			document.getElementById("muti").onclick = function(){
				if(gain.gain.value==0){
					gain.gain.value = 1;
					this.innerHTML = "静音"
				}else {
					gain.gain.value = 0;
					this.innerHTML = "取消静音"
				}
			}

			document.getElementById("musicFileBtn").onclick = function(){
				document.getElementById('musicFile').click();
			}

			//听作者的歌
			document.getElementById("author").onclick = function(){
				chooseMusic();
			}

			//选择audio作为播放源
			function chooseMusic(){
				if(bufferSource){
					bufferSource.disconnect(analyser);
					bufferSource.disconnect(AC.destination);
				}

				audio.src = musics[~~(Math.random()*musics.length)];
				audio.load();
				playMusic(audio);
			}

			//在下一次事件循环中执行动画
			setTimeout(function(){
				initAnimation();
			}, 0);

			//如果用户选取了自己的音乐则通过filereader读取
			document.getElementById('musicFile').onchange = function() {
				if(this.files.length == 0)return;

				if (!audio.ended || !audio.paused) audio.pause();

				if (bufferSource && ('stop' in bufferSource)) bufferSource.stop();

				if(audioSource){
					audioSource.disconnect(analyser);
					audioSource.disconnect(AC.destination);
				}

				paintStop = true;

				ctx.clearRect(0,0,canvas.width,canvas.height)
				ctx.save();
				ctx.fillStyle = "#FFF";
				ctx.textAlign = "center";
				ctx.textBaseline = "middle";
				ctx.font = "20px 微软雅黑";
				ctx.fillText("音频加载中..." , canvas.width/2 , canvas.height/2);
				ctx.restore();

				var fr = new FileReader();
				fr.readAsArrayBuffer(this.files[0]);
				fr.onload = function(e) {
					decodeBuffer(e.target.result , function(buffer){
						paintStop = false;
						playMusic(buffer);
					})
				};
			};

			//对音频buffer进行解码
			function decodeBuffer(arraybuffer , callback) {
				AC.decodeAudioData(arraybuffer, function(buffer) {
					callback(buffer);
				}, function(e) {
					alert("文件解码失败")
				})
			}

			//音频播放
			function playMusic(arg) {
				var source;
				//如果arg是audio的dom对象，则转为相应的源
				if(arg.nodeType){
					audioSource = audioSource || AC.createMediaElementSource(arg);
					source = audioSource;
				}else {
					bufferSource = AC.createBufferSource();

					bufferSource.buffer = arg;

					bufferSource.onended = function(){
						playMusic(arg)
					}

					//播放音频
					setTimeout(function(){bufferSource.start()}, 0);

					source = bufferSource;
				}

				//连接analyserNode
				source.connect(analyser);

				analyser.connect(gain);

				gain.connect(AC.destination);
			}

			//绘制音谱的参数
			var rt_array = [],	//用于存储柱形条对象
				rt_length = 30,		//规定有多少个柱形条
				outcanvas = null;

			var grd = ctx.createLinearGradient(0, 110, 0, 270);
			grd.addColorStop(0, "red");
			grd.addColorStop(0.3, "yellow");
			grd.addColorStop(1, "#00E800");

			//动画初始化，获取analyserNode里的音频buffer
			function initAnimation() {
				outcanvas = document.createElement("canvas");
				outcanvas.width = canvas.width;
				outcanvas.height = canvas.height / 2;

				//每个柱形条的宽度，及柱形条宽度+间隔
				var aw = canvas.width/rt_length;
				var w = aw - 5;

				for (var i = 0; i < rt_length; i++) {
					rt_array.push(new Retangle(w , 5 , i * aw , canvas.height / 2))
				}

				animate();
			}

			function animate() {
				if(!paintStop){
					ctx.clearRect(0, 0, canvas.width, canvas.height);
					
					//出来的数组为8bit整型数组，即值为0~256，整个数组长度为1024，即会有1024个频率，只需要取部分进行显示
					var array_length = analyser.frequencyBinCount;
					var array = new Uint8Array(array_length);
					analyser.getByteFrequencyData(array);	//将音频节点的数据拷贝到Uin8Array中

					//数组长度与画布宽度比例
					var bili = array_length/canvas.width;
					
					for (var i = 0; i < rt_array.length; i++) {
						var rt = rt_array[i];
						//根据比例计算应该获取第几个频率值，并且缓存起来减少计算
						rt.index = ('index' in rt) ? rt.index : ~~(rt.x * bili); 
						rt.update(array[rt.index]);
					}

					copy();
				}
				
				RAF(animate);
			}

			//制造半透明投影
			function copy() {
				var outctx = outcanvas.getContext("2d");
				var imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height / 2);
				for (var i = 0; i < imgdata.data.length; i += 4) {
					imgdata.data[i + 3] = 30;
				}
				outctx.putImageData(imgdata, 0, 0);
				ctx.save();
				ctx.translate(canvas.width / 2, canvas.height / 2);
				ctx.rotate(Math.PI);
				ctx.scale(-1, 1);
				ctx.drawImage(outcanvas, -canvas.width / 2, -canvas.height / 2)
				ctx.restore();
			}

			//音谱条对象
			 function Retangle(w, h, x, y) {
				this.w = w;
				this.h = h;
				this.x = x;
				this.y = y;
				this.jg = 3;
				this.power = 0;
				this.dy = y;
				this.num = 0;
			}

			var Rp = Retangle.prototype;

			Rp.update = function(power){
				this.power = power;
				this.num = ~~(this.power / this.h + 0.5);
				if (this.power > this.y - (this.dy + this.h)) {
					this.dy = this.y - this.power - this.h - 1;
				} else if (this.dy + this.h > this.y) {
					this.dy = this.y - this.h;
				} else {
					this.dy += 1;
				}

				this.draw();
			}

			Rp.draw = function(){
				ctx.fillStyle = grd;
				var h = (~~(this.power / (this.h + this.jg))) * (this.h + this.jg);
				ctx.fillRect(this.x, this.y - h, this.w, h)
				for (var i = 0; i < this.num; i++) {
					var y = this.y - i * (this.h + this.jg);
					ctx.clearRect(this.x - 1, y, this.w + 2, this.jg);
				}
				ctx.fillStyle = "#950000"
				ctx.fillRect(this.x, this.dy, this.w, this.h);
			}
		}())
	</script>
</body>
</html>